package com.coalmine.api.controller;


import com.alibaba.druid.pool.DruidPooledConnection;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.coalmine.api.domain.ApiConfig;
import com.coalmine.api.domain.ApiDatasource;
import com.coalmine.api.domain.ApiSql;
import com.coalmine.api.service.IApiConfigService;
import com.coalmine.api.service.IApiDatasourceService;
import com.coalmine.api.service.IApiGroupService;
import com.coalmine.api.util.JdbcUtil;
import com.coalmine.api.util.PoolManager;
import com.coalmine.api.util.RedisPoolManager;
import com.coalmine.api.util.SqlEngineUtil;
import com.coalmine.common.annotation.Log;
import com.coalmine.common.core.controller.BaseController;
import com.coalmine.common.core.domain.AjaxResult;
import com.coalmine.common.core.page.TableDataInfo;
import com.coalmine.common.enums.BusinessType;
import com.coalmine.common.utils.file.FileUtils;
import com.coalmine.common.utils.sql.SqlUtil;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import com.deepoove.poi.util.PoitlIOUtils;
import com.github.freakchick.orange.SqlMeta;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ResourceLoader;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import redis.clients.jedis.Jedis;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 *  api配置信息表 前端控制器
 * </p>
 *
 * @author 尚郑
 * @since 2022-04-01
 */

@Api(value="API基本信息",tags={"API管理接口"})
@RestController
@RequestMapping("/api/config")
public class ApiConfigController extends BaseController {

    @Autowired
    IApiConfigService apiConfigService;
    @Autowired
    IApiDatasourceService dataSourceService;
    @Autowired
    IApiGroupService groupService;

    @Value("${datamanage.api.context}")
    String apiContext;

    @GetMapping("/context")
    @ApiOperation("获取context")
    public String add() {
        return apiContext;
    }

    @Autowired
    ResourceLoader resourceLoader;

    /**
     * 新增API配置
     */
    @PreAuthorize("@ss.hasPermi('api:config:add')")
    @ApiOperation("新增API")
    @Log(title = "API管理", businessType = BusinessType.INSERT)
    @PostMapping("/add")
    public AjaxResult add(@RequestBody ApiConfig apiConfig) {
        String path = apiConfig.getPath();
        String regex = "^[a-zA-Z0-9_/-]+$";
        boolean flag= path.matches(regex);
        if(!flag){
            return AjaxResult.error("API路径只能出现字母数字-/_");
        }
        apiConfig.setCreateBy(getUsername());
        apiConfigService.add(apiConfig);
        return AjaxResult.success("新增api成功");
    }

    @ApiOperation("解析SQL参数")
    @PostMapping("/parseParam")
    @ApiImplicitParam(name = "sql", value = "sql参数", required = true,
            dataType = "String", dataTypeClass = String.class)
    public AjaxResult parseParam(String sql) {
        try {
            Set<String> set = SqlEngineUtil.getEngine().parseParameter(sql);
            //转化成前端需要的格式
            List<JSONObject> list = set.stream().map(t -> {
            JSONObject object = new JSONObject();
            object.put("value", t);
            return object;
            }).collect(Collectors.toList());
            return AjaxResult.success(list);
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
    }
    @PreAuthorize("@ss.hasPermi('api:config:list')")
    @GetMapping("/getAll")
    @ApiOperation("查询所有API")
    public TableDataInfo getAll() {
        startPage();
        List<ApiConfig> apiConfigList = apiConfigService.getAll();
        return getDataTable(apiConfigList);
    }

    @PreAuthorize("@ss.hasPermi('api:config:detail')")
    @GetMapping("/detail/{id}")
    @ApiOperation("API详情")
    @ApiImplicitParam(name = "id", value = "api信息id", required = true,
            dataType = "String", dataTypeClass = String.class)
    public AjaxResult detail(@PathVariable String id) {
        return AjaxResult.success(apiConfigService.detail(id));
    }

    //给前端使用的数据结构
    /*@RequestMapping("/getApiTree")
    public JSONArray getApiTree() {
        return apiConfigService.getAllDetail();
    }*/


    @PreAuthorize("@ss.hasPermi('api:config:search')")
    @PostMapping("/search")
    @ApiOperation("搜索API")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "keyword", value = "关键字", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "field", value = "表字段列名", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "groupId", value = "api分组id", dataType = "String", dataTypeClass = String.class)
    })
    public TableDataInfo search(String keyword, String field, String groupId) {
        startPage();
        List<ApiConfig> apiConfigList=apiConfigService.search(keyword, field, groupId);
        return getDataTable(apiConfigList);
    }

    @ApiOperation("删除API")
    @PreAuthorize("@ss.hasPermi('api:config:remove')")
    @Log(title = "API管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/delete/{id}")
    @ApiImplicitParam(name = "id", value = "api信息id", required = true,
            dataType = "String", dataTypeClass = String.class)
    public AjaxResult delete(@PathVariable String id) {
        return toAjax(apiConfigService.delete(id));
    }

    @ApiOperation("更新API")
    @PreAuthorize("@ss.hasPermi('api:config:edit')")
    @Log(title = "API管理", businessType = BusinessType.UPDATE)
    @PutMapping("/update")
    public AjaxResult update(@RequestBody ApiConfig apiConfig) {
        String path = apiConfig.getPath();
        String regex = "^[a-zA-Z0-9_/-]+$";
        boolean flag = path.matches(regex);
        if (!flag) {
            return AjaxResult.error("API路径不能出现特殊字符和中文");
        }
        apiConfig.setUpdateBy(getUsername());
        apiConfigService.update(apiConfig);
        return AjaxResult.success("更新api成功");
    }

    @ApiOperation("复制API")
    @ApiImplicitParam(name = "id", value = "api信息id", required = true,
            dataType = "String", dataTypeClass = String.class)
    @Log(title = "复制API", businessType = BusinessType.INSERT)
    @GetMapping("/copy/{id}")
    @PreAuthorize("@ss.hasPermi('api:config:copy')")
    public AjaxResult copyAPI(@PathVariable String id) {
        apiConfigService.copy(id);
        return AjaxResult.success("复制api成功");
    }

    @ApiOperation("API上线")
    @ApiImplicitParam(name = "id", value = "api信息id", required = true,
            dataType = "String", dataTypeClass = String.class)
    @Log(title = "API管理", businessType = BusinessType.UPDATE)
    @GetMapping("/online/{id}")
    @PreAuthorize("@ss.hasPermi('api:config:online')")
    public AjaxResult online(@PathVariable String id) {
        if (StringUtils.isBlank(id)) {
            return AjaxResult.success("参数不能为空");
        }
        String[] ids = id.split(",");
        for (int i = 0; i < ids.length; i++) {
            String path = apiConfigService.getPath(ids[i]);
            apiConfigService.online(ids[i], path);
        }
        return AjaxResult.success("发布api成功");
    }

    @PreAuthorize("@ss.hasPermi('api:config:offline')")
    @ApiOperation("API下线")
    @ApiImplicitParam(name = "id", value = "api信息id", required = true,
            dataType = "String", dataTypeClass = String.class)
    @Log(title = "API管理", businessType = BusinessType.UPDATE)
    @GetMapping("/offline/{id}")
    public AjaxResult offline(@PathVariable String id) {
        if (StringUtils.isBlank(id)) {
            return AjaxResult.success("参数不能为空");
        }
        String[] ids = id.split(",");
        for (int i = 0; i < ids.length; i++) {
            String path = apiConfigService.getPath(ids[i]);
            apiConfigService.offline(ids[i], path);
        }
        return AjaxResult.success("下线api成功");
    }

    @ApiOperation("获取IP端口")
    @GetMapping("/getIPPort")
    public AjaxResult getIPPort(HttpServletRequest request) {
        return AjaxResult.success(request.getServerName() + ":" + request.getServerPort() + "/" + apiContext);
    }

    @PreAuthorize("@ss.hasPermi('api:config:parse')")
    @ApiOperation("解析动态SQL")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "sql", value = "SQL语句", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "params", value = "参数", dataType = "json", dataTypeClass = cn.hutool.json.JSON.class)
    })
    @PostMapping("/parseDynamicSql")
    public AjaxResult parseDynamicSql(String sql, String params) {
        try {
            Map<String, Object> map = new HashMap<>();
            if (com.coalmine.common.utils.StringUtils.isJson(params)) {
                map = JSON.parseObject(params, Map.class);
            } else {
                JSONArray jsonArray = JSON.parseArray(params);
                map.put("list", jsonArray);
            }
            // Map<String, Object> map = JSON.parseObject(params, Map.class);
            SqlMeta sqlMeta = SqlEngineUtil.getEngine().parse(sql, map);
            return AjaxResult.success(sqlMeta);
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
    }
    @PreAuthorize("@ss.hasPermi('api:config:test')")
    @ApiOperation("运行SQL")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "datasourceId", value = "数据源id", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "sql", value = "sql语句", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "params", value = "参数", dataType = "String", dataTypeClass = String.class)
    })
    @PostMapping("/sql/execute")
    public AjaxResult executeSql(String datasourceId, String sql, String params) {
        DruidPooledConnection connection = null;
        try {
            ApiDatasource dataSource = dataSourceService.detail(datasourceId);
            if (com.coalmine.common.utils.StringUtils.equals("redis", dataSource.getType())) {
                Jedis jedis = RedisPoolManager.getRedisPooledConnection(dataSource);
                String redisKey = sql.substring(sql.lastIndexOf(" ") + 1);
                if (sql.contains("set")) {
                    jedis.set(redisKey, params);
                    return AjaxResult.success("set key success");
                } else {
                    String redisVal = jedis.get(redisKey);
                    return AjaxResult.success(redisVal);
                }
            }

            connection = PoolManager.getPooledConnection(dataSource);
            Map<String, Object> map = new HashMap<>();
            if (com.coalmine.common.utils.StringUtils.isJson(params)) {
                map = JSON.parseObject(params, Map.class);
            } else {
                JSONArray jsonArray = JSON.parseArray(params);
                map.put("list", jsonArray);
            }
            //Map<String, Object> map = JSON.parseObject(params, Map.class);
            //参数非法字符校验
            boolean flag = SqlUtil.sqlParamValidate(map);
            if (flag) {
                return AjaxResult.error(HttpServletResponse.SC_BAD_REQUEST, "参数可能存在SQL注入风险");
            }
            SqlMeta sqlMeta = SqlEngineUtil.getEngine().parse(sql, map);
            Object data = JdbcUtil.executeSql(connection, sqlMeta.getSql(), sqlMeta.getJdbcParamValues());
            return AjaxResult.success(data);
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        } finally {
            try {
                if (connection != null)
                    connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }



    //给前端使用的数据结构
    @ApiOperation("API树结构")
    @GetMapping("/getApiTree")
    public JSONArray getApiTree() {
        return apiConfigService.getAllDetail();
    }

    @ApiOperation("导出docx格式的API")
    @PreAuthorize("@ss.hasPermi('api:config:exportmd')")
    @Log(title = "API管理", businessType = BusinessType.EXPORT)
    @ApiImplicitParam(name = "ids", value = "api信息id集合,英文逗号分隔", required = true,
            dataType = "String", dataTypeClass = String.class)
    @PostMapping("/apiDocs")
    public void apiDocs(String ids, HttpServletResponse response) {
        List<String> collect = Arrays.asList(ids.split(","));
        try {
            List<Map<String, Object>> tList = apiConfigService.apiDocx(collect);
            // resource目录下的文件名
            String filePathName = FileUtils.getFilePathName("word/apiTemplate.docx");
            //渲染表格动态行
            LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
            Configure config = Configure.builder()
                    .bind("resultList", policy)
                    .bind("detailList", policy).build();
            XWPFTemplate template = XWPFTemplate.compile(filePathName, config).render(
                    new HashMap<String, Object>() {{
                        put("typeAPIDocx", tList);
                    }}
            );
            // 生成docx后缀格式的word
            String fileName = "API接口规范-标准版.docx";//文件名  带后缀
            response.setContentType("application/octet-stream");
            response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
            OutputStream out = response.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(out);
            template.write(bos);
            bos.flush();
            out.flush();
            PoitlIOUtils.closeQuietlyMulti(template, bos, out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @ApiOperation("导出JSON格式的API")
    @PreAuthorize("@ss.hasPermi('api:config:export')")
    @Log(title = "API管理", businessType = BusinessType.EXPORT)
    @ApiImplicitParam(name = "ids", value = "api信息id集合,英文逗号分隔", required = true,
            dataType = "String", dataTypeClass = String.class)
    @PostMapping("/downloadConfig")
    public void downloadConfig(String ids, HttpServletResponse response) {
        List<String> collect = Arrays.asList(ids.split(","));
        JSONObject jo = apiConfigService.selectBatch(collect);
        String s = jo.toString(SerializerFeature.WriteMapNullValue);
        response.setContentType("application/x-msdownload;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment; filename=api_config.json");
        OutputStream os = null;
        try {
            os = response.getOutputStream();
            os.write(s.getBytes("utf-8"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null)
                    os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @ApiOperation("导入JSON格式的API")
    @PreAuthorize("@ss.hasPermi('api:config:import')")
    @Log(title = "API管理", businessType = BusinessType.IMPORT)
    @ApiImplicitParam(name = "file", value = "JSON格式的API文件", required = true,
            dataType = "file", dataTypeClass = File.class)
    @PostMapping(value = "/import", produces = "application/json;charset=UTF-8")
    public AjaxResult uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
            String s = IOUtils.toString(file.getInputStream(), "utf-8");
            if (StringUtils.isBlank(s)) {
                return AjaxResult.error("文件不能为空");
            }
            JSONObject jsonObject = JSON.parseObject(s);
            List<ApiConfig> configs = JSON.parseArray(jsonObject.getJSONArray("api").toJSONString(), ApiConfig.class);
            List<ApiSql> apiSqlList = JSON.parseArray(jsonObject.getJSONArray("sql").toJSONString(), ApiSql.class);
            String userName=getUsername();
            apiConfigService.insertBatch(configs, apiSqlList,userName);
            return AjaxResult.success("导入api成功");
    }
}
